package com.aletto.general_store.data;

import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint8;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import io.reactivex.rxjava3.core.Observable;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;

public class ERC20ServiceAbi {

    public static String
            URL = "https://endpoints.omniatech.io/v1/bsc/testnet/public",
            MARKET_CONTRACT_ADDRESS = "0x32bf0b6238f313126e8c945ea686455ddc8e8109",
            USDT_CONTRACT_ADDRESS = "0x4847ff2615048B814F4BbEbB4cbF63b5b677f6b6";

    private static final byte chainId = BigInteger.valueOf(97).byteValue();

    private static final Web3j web3j = Web3j.build(
            new HttpService(
                    "https://endpoints.omniatech.io/v1/bsc/testnet/public",
                    new OkHttpClient.Builder()
                            .connectTimeout(15, TimeUnit.SECONDS)
                            .addNetworkInterceptor(
                                    new HttpLoggingInterceptor()
                                            .setLevel(HttpLoggingInterceptor.Level.BODY)
                            )
                            .build()
            )
    );


    public static Web3j get() {
        return Web3j.build(new HttpService(URL));
    }

    public static EthSendTransaction buy(
            BigDecimal price, Long orderId, int number, int goodsType, Credentials credentials, String sellerAddress, String contractAddress) throws IOException {

        Function function = new Function(
                "buy",
                Arrays.asList(
                        new Uint256(orderId), // 购买的商品编号
                        new Uint256(Convert.toWei(price, Convert.Unit.ETHER).toBigInteger()), // 商品价格
                        new Uint256(BigInteger.valueOf(number)), // 购买的数量
                        new Uint8(BigInteger.valueOf(goodsType)), // 购买的商品类型
                        new Address(sellerAddress)), // 卖家地址
                Collections.emptyList());

        return executeRemoteCallTransaction(function, credentials, contractAddress);
    }


    /**
     * 查询余额
     *
     * @param who 查询地址
     */
    public static Observable<BigDecimal> balance(String who) {
        return Observable.create(emitter -> {
            EthGetBalance ethGetBalance = web3j.ethGetBalance(who, DefaultBlockParameterName.LATEST).send();
            BigInteger balanceWei = ethGetBalance.getBalance();

            emitter.onNext(Convert.fromWei(balanceWei.toString(), Convert.Unit.ETHER));
            emitter.onComplete();
        });
    }

    /**
     * 查询ERC20余额
     *
     * @param credentials     查询地址
     * @param contractAddress 合约地址
     */
    public static Observable<BigDecimal> balance(Credentials credentials, String contractAddress) {
        return Observable.create(emitter -> {
            Function function = new Function(
                    "balanceOf",
                    Collections.singletonList(new Address(credentials.getAddress())),
                    Collections.singletonList(new TypeReference<Uint256>() {
                        // 泛型不可省略
                    })
            );

            Request<?, EthCall> ethCallRequest = web3j.ethCall(
                    Transaction.createEthCallTransaction(
                            credentials.getAddress(),
                            contractAddress,
                            FunctionEncoder.encode(function)
                    ),
                    DefaultBlockParameterName.LATEST
            );

            EthCall ethCall = ethCallRequest.send();
            List<Type> decodedResponse = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            Uint256 allowance = (Uint256) decodedResponse.get(0);

            emitter.onNext(Convert.fromWei(allowance.getValue().toString(), Convert.Unit.ETHER));
            emitter.onComplete();
        });

    }

    /**
     * 查看授权额
     *
     * @param contractAddress 合约地址
     * @param owner           所有者钱包地址
     * @param spender         授权地址
     */
    public static BigDecimal allowance(String contractAddress, String owner, String spender) throws IOException {
        Function function = new Function(
                "allowance",
                Arrays.asList(new Address(owner), new Address(spender)),
                Collections.singletonList(new TypeReference<Uint256>() {
                    // 泛型不可省略
                })
        );

        // 调用智能合约的allowance方法
        Request<?, EthCall> ethCallRequest = web3j.ethCall(
                Transaction.createEthCallTransaction(
                        owner,
                        contractAddress,
                        FunctionEncoder.encode(function)
                ),
                DefaultBlockParameterName.LATEST
        );

        EthCall ethCall = ethCallRequest.send();
        List<Type> decodedResponse = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());

        Uint256 allowance = (Uint256) decodedResponse.get(0);
        return Convert.fromWei(allowance.getValue().toString(), Convert.Unit.ETHER);
    }


    /**
     * 授权
     *
     * @param credentials     钥匙
     * @param spenderAddress  地址
     * @param contractAddress 地址
     * @param value           值
     */
    public static EthSendTransaction approve(Credentials credentials, String spenderAddress, String contractAddress, BigInteger value) throws IOException {
        Function function = new Function(
                "approve",
                Arrays.asList(new Address(spenderAddress), new Uint256(value)),
                Collections.emptyList()
        );

        return executeRemoteCallTransaction(function, credentials, contractAddress);
    }


    /**
     * 买家发起仲裁
     * @param orderId   订单编号
     */
    public static EthSendTransaction raiseArbitration(Long orderId, String privateKey, String contractAddress) throws IOException {
        Function function = new Function(
                "raiseArbitration",
                Collections.singletonList(
                        new Uint256(orderId)
                ),
                Collections.emptyList()
        );

        return executeRemoteCallTransaction(function, Credentials.create(privateKey), contractAddress);
    }

    // 买家取消仲裁
    public static EthSendTransaction cancelArbitration(Long orderId, String privateKey, String contractAddress) throws IOException {
        Function function = new Function(
                "cancelArbitration",
                Collections.singletonList(new Uint256(orderId)), // 订单编号
                Collections.emptyList()
        );

        return executeRemoteCallTransaction(function, Credentials.create(privateKey), contractAddress);
    }

    // 商家同意退款
    public static EthSendTransaction refund(Long orderId, String privateKey, String contractAddress) throws IOException {
        Function function = new Function(
                "refund",
                Collections.singletonList(
                        new Uint256(orderId) // 订单编号
                ),
                Collections.emptyList());

        return executeRemoteCallTransaction(function, Credentials.create(privateKey), contractAddress);
    }

    // 商家取回货款
    public static EthSendTransaction takeFund(Long orderId, String privateKey, String contractAddress) throws IOException {
        Function function = new Function(
                "takeFund",
                Collections.singletonList(
                        new Uint256(orderId) // 订单编号
                ),
                Collections.emptyList());

        return executeRemoteCallTransaction(function, Credentials.create(privateKey), contractAddress);
    }

    /**
     * 客服申请仲裁
     */
    public static EthSendTransaction resolveArbitration(Long orderId, boolean is, String privateKey, String contractAddress) throws IOException {
        Function function = new Function(
                "resolveArbitration",
                Arrays.asList(
                        new Uint256(orderId), // 订单编号
                        new Bool(is) // true:商家赢，false：买家赢
                ),
                Collections.emptyList());

        return executeRemoteCallTransaction(function, Credentials.create(privateKey), contractAddress);
    }

    private static EthSendTransaction executeRemoteCallTransaction(Function function, Credentials credentials, String contractAddress) throws IOException {
        String data = FunctionEncoder.encode(function);

        // nonce
        EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(credentials.getAddress(), DefaultBlockParameterName.LATEST).send();
        BigInteger nonce = ethGetTransactionCount.getTransactionCount();

        // gas
        EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
        BigInteger gasEstimation = ethGasPrice.getGasPrice();

        // gas limit
        Transaction transaction = Transaction.createEthCallTransaction(credentials.getAddress(), contractAddress, data);
        EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
        BigInteger amountUsed = ethEstimateGas.getAmountUsed();
        BigInteger gasLimit = BigDecimal.valueOf(amountUsed.longValue()).multiply(new BigDecimal("3")).toBigInteger();

        // 判断是否有足够Gas进行合约操作
        BigInteger balance = web3j.ethGetBalance(credentials.getAddress(), DefaultBlockParameterName.LATEST).send().getBalance();
        if (balance.compareTo(gasLimit) < 0) {
            throw new org.web3j.tx.exceptions.ContractCallException("Gas不足");
        }

        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasEstimation, gasLimit, contractAddress, data);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);

        return web3j.ethSendRawTransaction(Numeric.toHexString(signedMessage)).send();
    }

}
